#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include <map>
#include "ImaGlobals.h"
#include "ImaRequestProcessor.h"
#include "ImaConstants.h"
#include "ImaConf.h"
#include "ImaDbInterface.h"

#define MAXEVENTS 64


typedef struct jsondata {
	union {
		char buffer[2];
		int nSize;
	} sizeofdata;
	int nBytesRead;
	char* pData;
} JSONDATA;

typedef std::map<int, JSONDATA*> FD_TO_CLIENT_DATA_MAP;

/******************************************************************************
* Globals declared here
******************************************************************************/
ImaLogger* gpLogger = NULL;
ImaDbInterface* gpDbInterface = NULL;
ImaMqInterface* gpMqInterface = NULL;

int ima_init_globals(char* pConfFilename) {
	ImaConf confObj(pConfFilename);
	if(confObj.parseConfFile() != 0) {
		//LOG(error, "Failed to parse conf file");
		fprintf(stderr, "Failed to parse conf file\n");
		exit(EXIT_FAILURE);
	}

	gpLogger = ImaLogger::getInstance("log4cpp.properties");

	if(gpLogger == NULL) {
		return -1;
	}

	char log_msg[1024];
	sprintf(log_msg, "inside ima_init_globals");
	LOG(debug, __FILE__, __LINE__, log_msg);
	gpDbInterface = ImaDbInterface::getInstance(confObj);
	if(gpDbInterface == NULL) {
		LOG(error, __FILE__, __LINE__, "Failed to connect to DB\n");
		return -1;
	}
	
	gpMqInterface = ImaMqInterface::getInstance(confObj);
	if(gpMqInterface == NULL) {
		LOG(error, __FILE__, __LINE__, "Failed to create or connect to message queue\n");
		return -1;
	}

	return 0;
}

static int
read_size_of_receivable_data(int fd, JSONDATA* pJsonData) {
	ssize_t received = 0;

	while(pJsonData->nBytesRead != 2) {
		received = read(fd, pJsonData->sizeofdata.buffer, 2-pJsonData->nBytesRead);
		if (received == -1) {
			/* If errno == EAGAIN, that means we have read all
			data. So go back to the main loop. */
			if (errno != EAGAIN) {
				perror ("read");
				return -1;
			}
			break;
		} else if (received == 0) {
			/* End of file. The remote has closed the
			connection. */
			return -1;
		} else {
			pJsonData->nBytesRead += received;
		}
	}

	if(pJsonData->nBytesRead > 2) {
		printf("Something went wrong while reading\n");
		return -1;
	} else if(pJsonData->nBytesRead < 2) {
		return 0;
	}

	return 2;
}

static int
read_receivable_data(int fd, JSONDATA* pJsonData) {
	if(pJsonData == NULL) {
		//TODO: log the error and return -1;
		//gpLogger->log(DEBUG, "pJsonData is NULL");
		LOG(debug, __FILE__, __LINE__, "pJsonData is NULL");
		return -1;
	}

	while (1) {
		ssize_t count = 0;
		char buf[512];
		count = read (fd, buf, sizeof(buf));
		if (count == -1) {
			/* If errno == EAGAIN, that means we have read all
			   data. So go back to the main loop. */
			if (errno != EAGAIN) {
				perror ("read");
				return -1;
			} else {
				return 0;
			}
		} else if (count == 0) {
			/* End of file. The remote has closed the
			   connection. */
			return -1;
		} else if(count > 0) {
			memcpy(pJsonData->pData + (pJsonData->nBytesRead - 2), buf, count);
			pJsonData->nBytesRead += count;
			if((pJsonData->nBytesRead - 2) == pJsonData->sizeofdata.nSize) {
				return pJsonData->nBytesRead - 2;
			} 
		}
	}

	return 0;
}

static int
write_data_on_socket(int nSockFd, const char* pData, size_t nLen) {
	LOG(debug, __FILE__, __LINE__, "write_data_on_socket called");

	int nTotalSent = 0;
	while (nTotalSent < nLen) {
		ssize_t count = 0;
		count = write(nSockFd, pData + nTotalSent, nLen - nTotalSent);
		if (count == -1) {
			/* If errno == EAGAIN, that means we have written all
			   data. So go back to the main loop. */
			if (errno != EAGAIN) {
				perror ("write");
				return -1;
			} else {
				continue;
			}
		} else if (count == 0) {
			/* End of file. The remote has closed the
			   connection. */
			return -1;
		} else if(count > 0) {
			nTotalSent += count;
		}
	}

	return nTotalSent;
}


static int
send_response(int nSockFd, const char* pData, size_t nLen) {
	LOG(debug, __FILE__, __LINE__, "send_response called");

	short sizeOfDataToSend = nLen;
	if(write_data_on_socket(nSockFd, (const char*) &sizeOfDataToSend, 2) < 0) {
		LOG(error, __FILE__, __LINE__, "Failed to send the size of response data"); 
		return -1;
	}

	if(write_data_on_socket(nSockFd, pData, nLen) < 0) {
		LOG(error, __FILE__, __LINE__, "Failed to send the response data");
		return -1;
	}

	LOG(debug, __FILE__, __LINE__, "Sent the response data to the client.");

	return 0;	
}

static int
make_socket_non_blocking (int sfd) {
	int flags, s;

	flags = fcntl (sfd, F_GETFL, 0);
	if (flags == -1) {
      perror ("fcntl");
      return -1;
	}

	flags |= O_NONBLOCK;
	s = fcntl (sfd, F_SETFL, flags);
	if (s == -1) {
		perror ("fcntl");
		return -1;
	}
	
	return 0;
}

static int
create_and_bind (char *port) {
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int s, sfd;
	
	memset (&hints, 0, sizeof (struct addrinfo));
	hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
	hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
	hints.ai_flags = AI_PASSIVE;     /* All interfaces */
	
	s = getaddrinfo (NULL, port, &hints, &result);
	if (s != 0) {
		fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
		return -1;
	}
	
	for (rp = result; rp != NULL; rp = rp->ai_next) {
		sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sfd == -1)
			continue;

		// Disable Nagle's algorithm
		int nOptVal = 1;
		if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&nOptVal, sizeof(nOptVal))) {
      	fprintf(stderr, "ERROR - socketOptions: Error at setsockopt. (Reason: %s)\n", strerror(errno));
		}

		s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
		if (s == 0) {
			/* We managed to bind successfully! */
			break;
		}
		close (sfd);
	}
	
	if (rp == NULL) {
		fprintf (stderr, "Could not bind\n");
		return -1;
	}
	
	freeaddrinfo (result);
	
	return sfd;
}

//int test_log4cpp() {
// log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
// appender1->setLayout(new log4cpp::BasicLayout());
//
// log4cpp::Appender *appender2 = new log4cpp::FileAppender("default", "program.log");
// appender2->setLayout(new log4cpp::BasicLayout());
//
// log4cpp::Category& root = log4cpp::Category::getRoot();
// root.setPriority(log4cpp::Priority::WARN);
// root.addAppender(appender1);
//
// log4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1"));
// sub1.addAppender(appender2);
//
// // use of functions for logging messages
// root.error("root error");
// root.info("root info");
// sub1.error("sub1 error");
// sub1.warn("sub1 warn");
//
// // printf-style for logging variables
// root.warn("%d + %d == %s ?", 1, 1, "two");
//
// // use of streams for logging messages
// root << log4cpp::Priority::ERROR << "Streamed root error";
// root << log4cpp::Priority::INFO << "Streamed root info";
// sub1 << log4cpp::Priority::ERROR << "Streamed sub1 error";
// sub1 << log4cpp::Priority::WARN << "Streamed sub1 warn";
//
// // or this way:
// root.errorStream() << "Another streamed error";
//
// return 0;
//}
//
//int test_log4cpp_with_file() {
//	std::string initFileName = "log4cpp.properties";
//	log4cpp::PropertyConfigurator::configure(initFileName);
//
//	log4cpp::Category& root = log4cpp::Category::getRoot();
//
//	log4cpp::Category& sub1 = 
//		log4cpp::Category::getInstance(std::string("sub1"));
//
//	//log4cpp::Category& sub2 = 
////		log4cpp::Category::getInstance(std::string("sub1.sub2"));
//
//	root.warn("Storm is coming");
//
//	sub1.debug("Received storm warning");
//	sub1.info("Closing all hatches");
//
//	//sub2.debug("Hiding solar panels");
//	//sub2.error("Solar panels are blocked");
//	//sub2.debug("Applying protective shield");
//	//sub2.warn("Unfolding protective shield");
//	//sub2.info("Solar panels are shielded");
//
//	sub1.info("All hatches closed");
//
//	root.info("Ready for storm.");
//
//	log4cpp::Category::shutdown();
//
//	return 0;
//}

/* Function 	:	daemonize
 * Description	:	this function will demonize the process
 * Parametres 	: 	file descriptor, event and query
 * Return values:
 */
int daemonize(int nNochdir, int nNoclose) {
	int nfd;
	
	// create a new process and exit
	switch (fork()) {
		case -1:	//error 
			std::cout<<"error\n";
			return (-1);
		case 0:	//child process
			std::cout<<"child process\n";
			break;
		default: //parent process
			std::cout<<"parentprocess\n";
			_exit(0);
	}
	
	// run the program in a new session
	if(setsid() == -1) {
		std::cout<<"setsid failed\n";
		return (-1);
	}
	
	// change the dierctory to '/'
	if(!nNochdir) {
		std::cout<<"calling chdir\n";
		(void)chdir("/");
	}
	
	if(!nNoclose && (nfd = open("/dev/null", O_RDWR, 0)) != -1) {
		(void)dup2(nfd, STDIN_FILENO);
		(void)dup2(nfd, STDOUT_FILENO);
		(void)dup2(nfd, STDERR_FILENO);
		if (nfd > 2) {
			(void)close(nfd);
		}
	}

	std::cout<<"will return 0 i.e. success\n";
	return (0);
}


int
main(int argc, char *argv[]) {
	int sfd, s;
	int efd;
	struct epoll_event event;
	struct epoll_event *events;
	
	if (argc < 3) {
		fprintf (stderr, "Usage: %s [-b] [-c conf file name]\n", argv[0]);
		exit (EXIT_FAILURE);
	}

	int nCh;
	int nDaemonize = 0;
	char* pConfFilename = IMA_CONF_FILE;
	while ((nCh = getopt(argc, argv, "bc:")) != -1) {
		switch(nCh) {
			case 'b':
				nDaemonize = 1;
				break;
			case 'c':
				pConfFilename = optarg;
				break;
			case 'h':
			default:
				fprintf (stderr, "Usage: %s [-c config file name] [-h]\n", argv[0]);
				exit(EXIT_FAILURE);
		}
	}

	/* Accessing of the Config file, if the config file exists then parse the config file */
	if (access(pConfFilename, R_OK) != 0) {
		fprintf(stderr, "Conf file doesnt exist\n");
		exit(EXIT_FAILURE);
	}

	ImaConf confObj(pConfFilename);
	if(confObj.parseConfFile() != 0) {
		fprintf(stderr, "Failed to parse conf file\n");
		exit(EXIT_FAILURE);
	}

	if(ima_init_globals(pConfFilename) != 0) {
		fprintf(stderr, "Failed to initialize globals\n");
		exit(EXIT_FAILURE);
	}

	/* Begin Daemonize if requested */
	if(nDaemonize) {
		if (daemonize(0, 0) == -1) {
			printf( "ERROR - main: unable to daemonize\n");
			exit(EXIT_FAILURE);
		}
	}
	//test_log4cpp();
	//test_log4cpp_with_file();
	char log_msg[1024];
	sprintf(log_msg, "KN: Logger instance created");
	LOG(debug, __FILE__, __LINE__, log_msg);
	//LOG(debug, __FILE__, __LINE__, "Logger instance created");
	char strPort[16];
	sprintf(strPort, "%d", confObj.mListeningPort);
	sfd = create_and_bind (strPort);
	if (sfd == -1)
		abort();
	
	s = make_socket_non_blocking (sfd);
	if (s == -1)
		abort();
	
	s = listen (sfd, SOMAXCONN);
	if (s == -1) {
		perror ("listen");
		abort ();
	}
	
	efd = epoll_create1 (0);
	if (efd == -1) {
		perror ("epoll_create");
		abort ();
	}
	
	event.data.fd = sfd;
	event.events = EPOLLIN | EPOLLET;
	s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
	if (s == -1) {
		perror ("epoll_ctl");
		abort ();
	}
	
	/* Buffer where events are returned */
	events = (epoll_event*)calloc (MAXEVENTS, sizeof event);

	FD_TO_CLIENT_DATA_MAP fdToClientDataMap;
	
	/* The event loop */
	while (1) {
		int n, i;
	
		n = epoll_wait (efd, events, MAXEVENTS, -1);
		for (i = 0; i < n; i++) {
			if ((events[i].events & EPOLLERR) ||
				(events[i].events & EPOLLHUP) ||
				(!(events[i].events & EPOLLIN))) {
		      /* An error has occured on this fd, or the socket is not
		      ready for reading (why were we notified then?) */
				fprintf (stderr, "epoll error\n");
				close (events[i].data.fd);
				continue;
			} else if (sfd == events[i].data.fd) {
				/* We have a notification on the listening socket, which
				means one or more incoming connections. */
				while (1) {
					struct sockaddr in_addr;
					socklen_t in_len;
					int infd;
					char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
					
					in_len = sizeof in_addr;
					infd = accept (sfd, &in_addr, &in_len);
					if (infd == -1) {
						if ((errno == EAGAIN) ||
							(errno == EWOULDBLOCK)) {
							/* We have processed all incoming
							connections. */
							break;
						} else {
							perror ("accept");
							break;
						}
					}
					
					s = getnameinfo (&in_addr, in_len,
							hbuf, sizeof hbuf,
							sbuf, sizeof sbuf,
							NI_NUMERICHOST | NI_NUMERICSERV);
					if (s == 0) {
						printf("Accepted connection on descriptor %d "
							"(host=%s, port=%s)\n", infd, hbuf, sbuf);
					}
	
					/* Make the incoming socket non-blocking and add it to the
					 list of fds to monitor. */
					s = make_socket_non_blocking (infd);
					if (s == -1)
					  abort ();
					
					event.data.fd = infd;
					event.events = EPOLLIN | EPOLLET;
					s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
					if (s == -1) {
						perror ("epoll_ctl");
						abort ();
					}
				}
				continue;
			} else {
				/* We have data on the fd waiting to be read. Read and
				display it. We must read whatever data is available
				completely, as we are running in edge-triggered mode
				and won't get a notification again for the same
				data. */
				//int done = 0;

				JSONDATA* pJsonData = NULL;
				FD_TO_CLIENT_DATA_MAP::iterator it = fdToClientDataMap.find(events[i].data.fd);
				if(it == fdToClientDataMap.end()) {
					pJsonData = (JSONDATA*) malloc(sizeof(JSONDATA));
					memset(pJsonData, 0, sizeof(JSONDATA));
					fdToClientDataMap[events[i].data.fd] = pJsonData;
				} else {
					pJsonData = it->second;
				}

				if(pJsonData->nBytesRead < 2) {
					int size_of_data = read_size_of_receivable_data(events[i].data.fd, pJsonData);
					if(size_of_data < 0) {
						fdToClientDataMap.erase(events[i].data.fd);
						close (events[i].data.fd);
						continue;
					} else if(size_of_data == 0) {
						continue;
					}
				}

				printf("size of receivable data is %d\n", pJsonData->sizeofdata.nSize);
				pJsonData->pData = (char*) malloc(pJsonData->sizeofdata.nSize + 1);
				memset(pJsonData->pData, 0, sizeof(pJsonData->sizeofdata.nSize + 1));
				int nBytesRead = read_receivable_data(events[i].data.fd, pJsonData);
			
				if(nBytesRead >= 0) {
					pJsonData->nBytesRead += nBytesRead;
					if(pJsonData->nBytesRead - 2 >= pJsonData->sizeofdata.nSize) {
						pJsonData->pData[pJsonData->sizeofdata.nSize] = '\0';	
						//TODO process this json packet
						char logMsg[256];
						sprintf(logMsg, "json data received on socket %d", events[i].data.fd);
						LOG(debug,  __FILE__, __LINE__, logMsg);
						LOG(debug,  __FILE__, __LINE__, "Data received is:");
						LOG(debug,  __FILE__, __LINE__, pJsonData->pData);

						ImaRequestProcessor reqProcessor;
						char* pResp = reqProcessor.processRequest(pJsonData->pData);

						if(send_response(events[i].data.fd, pResp, strlen(pResp)) != 0) {
							LOG(error,  __FILE__, __LINE__, "Failed to send response to the client.");
						} else {
							LOG(debug,  __FILE__, __LINE__, "Response successfully sent to the client");
						}

						free(pResp); pResp = NULL;

						fdToClientDataMap.erase(events[i].data.fd);
						close(events[i].data.fd);
						continue;
						
					} else {
						continue;
					}
				} else if(nBytesRead == -1) {
					fdToClientDataMap.erase(events[i].data.fd);
					close (events[i].data.fd);
				}

				free(pJsonData->pData);
				free(pJsonData);
				pJsonData = NULL;
			}
		}
	}
	
	free (events);
	
	close (sfd);
	
	return EXIT_SUCCESS;
} 


#if 0
int
main (int argc, char *argv[]) {
	int sfd, s;
	int efd;
	struct epoll_event event;
	struct epoll_event *events;
	
	if (argc != 2) {
		fprintf (stderr, "Usage: %s [port]\n", argv[0]);
		exit (EXIT_FAILURE);
	}
	
	sfd = create_and_bind (argv[1]);
	if (sfd == -1)
		abort();
	
	s = make_socket_non_blocking (sfd);
	if (s == -1)
		abort();
	
	s = listen (sfd, SOMAXCONN);
	if (s == -1) {
		perror ("listen");
		abort ();
	}
	
	efd = epoll_create1 (0);
	if (efd == -1) {
		perror ("epoll_create");
		abort ();
	}
	
	event.data.fd = sfd;
	event.events = EPOLLIN | EPOLLET;
	s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
	if (s == -1) {
		perror ("epoll_ctl");
		abort ();
	}
	
	/* Buffer where events are returned */
	events = (epoll_event*)calloc (MAXEVENTS, sizeof event);
	
	/* The event loop */
	while (1) {
		int n, i;
	
		n = epoll_wait (efd, events, MAXEVENTS, -1);
		for (i = 0; i < n; i++) {
			if ((events[i].events & EPOLLERR) ||
				(events[i].events & EPOLLHUP) ||
				(!(events[i].events & EPOLLIN))) {
		      /* An error has occured on this fd, or the socket is not
		      ready for reading (why were we notified then?) */
				fprintf (stderr, "epoll error\n");
				close (events[i].data.fd);
				continue;
			} else if (sfd == events[i].data.fd) {
				/* We have a notification on the listening socket, which
				means one or more incoming connections. */
				while (1) {
					struct sockaddr in_addr;
					socklen_t in_len;
					int infd;
					char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
					
					in_len = sizeof in_addr;
					infd = accept (sfd, &in_addr, &in_len);
					if (infd == -1) {
						if ((errno == EAGAIN) ||
							(errno == EWOULDBLOCK)) {
							/* We have processed all incoming
							connections. */
							break;
						} else {
							perror ("accept");
							break;
						}
					}
					
					s = getnameinfo (&in_addr, in_len,
							hbuf, sizeof hbuf,
							sbuf, sizeof sbuf,
							NI_NUMERICHOST | NI_NUMERICSERV);
					if (s == 0) {
						printf("Accepted connection on descriptor %d "
							"(host=%s, port=%s)\n", infd, hbuf, sbuf);
					}
	
					/* Make the incoming socket non-blocking and add it to the
					 list of fds to monitor. */
					s = make_socket_non_blocking (infd);
					if (s == -1)
					  abort ();
					
					event.data.fd = infd;
					event.events = EPOLLIN | EPOLLET;
					s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
					if (s == -1) {
						perror ("epoll_ctl");
						abort ();
					}
				}
				continue;
			} else {
				/* We have data on the fd waiting to be read. Read and
				display it. We must read whatever data is available
				completely, as we are running in edge-triggered mode
				and won't get a notification again for the same
				data. */
				int done = 0;
		
				while (1) {
					ssize_t count;
					char buf[512];
					count = read (events[i].data.fd, buf, sizeof buf);
					if (count == -1) {
						/* If errno == EAGAIN, that means we have read all
						data. So go back to the main loop. */
						if (errno != EAGAIN) {
							perror ("read");
							done = 1;
						}
						break;
					} else if (count == 0) {
						/* End of file. The remote has closed the
						connection. */
						done = 1;
						break;
					}
	
					/* Write the buffer to standard output */
					s = write (1, buf, count);
					if (s == -1) {
						perror ("write");
						abort ();
					}
				}
		
				if (done) {
					printf ("Closed connection on descriptor %d\n", events[i].data.fd);
					/* Closing the descriptor will make epoll remove it
					from the set of descriptors which are monitored. */
					close (events[i].data.fd);
				}
			}
		}
	}
	
	free (events);
	
	close (sfd);
	
	return EXIT_SUCCESS;
} 
#endif
